
#include "khan.hpp"
#include "cmd_mng.hpp"
#include "util.hpp"

static const std::string OPT_START = "(";
static const std::string OPT_END   = ")";
static const std::string MULTI_OPT = "|";

static const std::string CMD_LINE_DELIM = " \t";
static const std::string CMD_DELIM = " \t\r\n";
static const std::string OPT_DELIM = " ()|\r\n";
static const std::string HELP_DELIM = "\r\n";

typedef boost::shared_ptr<string_vector> strvec_ptr;
typedef std::vector<strvec_ptr> cmd_tree_t;
typedef cmd_tree_t::iterator cmd_tree_iterator;

cmd_mng_ptr cmd_mng::_instance;
boost::mutex cmd_mng::_singletone_lock;

cmd_mng_ptr 
cmd_mng::instance() 
{
    if (_instance.use_count() == 0) {
        boost::mutex::scoped_lock scoped_lock(_singletone_lock);
        if (_instance.use_count() == 0) 
            _instance.reset(new cmd_mng());
    }
    return _instance;
};

cmd_mng::cmd_mng():
    _root_mode(new command_mode("_ROOT_", "_ROOT_"))
{ 
    _root_mode->set_parent(_root_mode);
}

cmd_mng::~cmd_mng()
{ }

bool is_runnable(cmd_list_iterator b, cmd_list_iterator e)
{
    cmd_list_iterator iter = b;
    for (; iter != e; iter++) {
        if((*iter)->is_excutable())
            return true;
    }
    return false;
}

bool cmd_mng::find_mode(const std::string& modestr, command_mode_ptr& mode) 
{
    boost::mutex::scoped_lock lock(_mode_lock);
    mode_map_iterator  iter =  _mode_map.find(modestr);
    if (_mode_map.end() == iter) {
        PDBG("%s is not register mdoe!!!!!!!\n", modestr.c_str());
        return false;
    }
    mode = iter->second;
    return true;
}

bool cmd_mng::add_mode(const std::string name, const std::string prompt, bool is_auth)
{
    return this->add_mode(name, prompt, _root_mode, is_auth);
}

bool cmd_mng::add_mode(const std::string name, const std::string prompt, 
        cmd_mode_ptr p, bool is_auth)
{
    command_mode_ptr tmp;
    if (find_mode(name, tmp)) return false;

    boost::mutex::scoped_lock lock(_mode_lock);
    tmp.reset(new command_mode(p, name, prompt, is_auth));
    _mode_map[name] = tmp;
    PDBG("add mode: %s\n", name.c_str());
    return tmp;
}

bool cmd_mng::add_command(const std::string modestr, const std::string cmd, 
        const std::string help, ExcuteHandle run)  
{
    bool    ret = false;
    string_vector      cmdvec;
    string_vector      helpvec;
    command_mode_ptr mode;
    mode_map_iterator    iter;

    kstrtok(help, HELP_DELIM, helpvec); /* FIXME */
    kstrtok(cmd, CMD_DELIM, cmdvec);

    if (!find_mode(modestr, mode)) {    
        return false;
    }

    /* XXX: mode <-> cmd lock */
    ret = make_cmdtree(mode->get_root(), cmd, help, run);

    PDBG("RET: %s add cmd: %s\n", (ret == true) ? "true" : "false" , cmd.c_str());
    return  ret;
}


bool cmd_mng::describe_command(vty_ptr vty, const std::string& cmdline, cmd_list_t& retlist, 
        bool is_child, bool& is_exec)
{
    string_vector args;
    return find_tree_leaf(vty, cmdline, retlist, args, is_child, is_exec);
}

bool cmd_mng::excute_command(vty_ptr vty, const std::string& cmdline)
{
    bool is_exec=false;
    cmd_ptr cmdptr;
    cmd_mode_ptr modeptr;
    cmd_list_t  cmdlist, exactlist;
    string_vector cmdvec, args;
    std::string rcmd;

    kstrtok(cmdline, CMD_DELIM, cmdvec);

    size_t cmdvec_size = cmdvec.size();
    if (cmdvec_size == 0) return false;
    PDBG("line >> %s\n", cmdline.c_str());

    if (find_tree_leaf(vty, cmdline, cmdlist, args, false, is_exec) != true)
        return false;

    PDBG("cmd number is %d !!!\n", (int)cmdlist.size());
    size_t cnum = cmdlist.size();
    if (cnum != 1) { 
		cmd_list_iterator iter = cmdlist.begin();
		int mnum=0, opt_num = args.size();
		PDBG(">>> args[%d] %s\n", args.size(), args[args.size() -1].c_str());
		for (; iter != cmdlist.end(); iter++) {
			if ((*iter)->mathed(args[opt_num-1]) == exact_match) {
				cmdptr = (*iter); mnum++;
			}
		}
		PDBG("exact_mached number is %d !!!\n", mnum);
		if (mnum != 1) return false;
	} else {
		cmdptr = (*cmdlist.begin());
	}
    
    if (!cmdptr->is_excutable()) {
        PDBG("%s is not excutable cmd!!\n", cmdptr->get_cmd().c_str());
        return false;
    }
    search_vector(args.begin(), args.end(), print_string);
    cmdptr->do_command(vty, cmdline, args); /* FIXME */
    //cmdptr->do_command(cmdline, args); /* FIXME */
    return true;
}

bool cmd_mng::find_tree_leaf(vty_ptr vty, const std::string& cmdline, cmd_list_t& retlist, string_vector& args,
        bool is_child, bool& is_exec)
{
    bool ret_tmp = false;
    cmd_ptr cmdptr, parent;
    cmd_mode_ptr modeptr;
    cmd_list_t  cmdlist, exactlist;
    string_vector cmdvec;
    int idx=0, cmdtype=0 , strtype=0, inttype=0, ipv4type=0;

    kstrtok(cmdline, CMD_DELIM, cmdvec);

    if (!find_mode(vty->current_mode(), modeptr)) 
        throw std::runtime_error("mode is not exist !!!!\n");

    parent = cmdptr = modeptr->get_root();
    size_t cmdvec_size = cmdvec.size();
    if (cmdvec_size == 0) {
        retlist = cmdptr->get_children();
        return true;
    }
    
    PDBG("line >> %s\n", cmdline.c_str());
    if ((is_child == false) && cmdvec_size == 1) {	/* FIXME */
        int num = cmdptr->find_children(cmdvec[0], retlist);
        if (num == 0) 
            return false;
			args.push_back((*retlist.begin())->get_cmd());
        return true;
    }
    if (!is_child ) {
        cmdvec_size = cmdvec_size -1;
    }

    PDBG("is_child(%s), cmdvec_size: %d\n", (is_child) ? "true" : "false" , (int)cmdvec_size);
    for (idx=0; idx < cmdvec_size; 
            idx++, cmdlist.clear(), exactlist.clear(), 
            cmdtype=0, strtype=0, inttype=0, ipv4type=0) 
    {
        if(idx != 0 && cmdptr->get_type() == CMD_TYPE)  {
            PDBG("ch: %s\n", cmdptr->get_cmd().c_str());
            args.push_back(cmdptr->get_cmd());
        }
        else if (idx != 0) {
            PDBG(">> ch: %s\n", cmdvec[idx-1].c_str());
            args.push_back(cmdvec[idx-1]);
        }

        int num = cmdptr->find_children(cmdvec[idx], cmdlist);
        PDBG("matched child num: %d\n", num);
        if (num == 0) { 
            PDBG("(%s) no matched cmd!!\n", cmdvec[idx].c_str());
            return false;
        }
        ret_tmp = is_runnable(cmdlist.begin(), cmdlist.end());

        if (num == 1) {
            cmdptr = (*cmdlist.begin());
            continue ;
        } 
        int exnum = cmdptr->find_children(exact_match, cmdvec[idx], exactlist);
        if (exnum == 1) {
            cmdptr = (*exactlist.begin());
            continue;
        }
        if (exnum > 1) { 
            PDBG("(%s) is exact mated number more than 1 !!!!!!!\n", cmdvec[idx].c_str());
            return false;
        }
        cmd_list_iterator iter = cmdlist.begin();
        PDBG("+++++++++++++++++++++++++++++++\n");
        for (; iter != cmdlist.end(); iter++) {
            PDBG("(%s) is type (%d)!\n", 
                    (*iter)->get_cmd().c_str(), 
                    (*iter)->get_type());
            PDBG("check >>>> (%d, %d, %d)\n", cmdtype, inttype, ipv4type);
            
            if ((*iter)->get_type() == CMD_TYPE) {
                cmdtype++;
                PDBG("(%s) is cmd_type, num(%d)!\n", (*iter)->get_cmd().c_str(), cmdtype);
            }
            else if ((*iter)->get_type() == INT_RANGE_TYPE) inttype++;
            else if ((*iter)->get_type() == HEX_RANGE_TYPE) inttype++;
            else if ((*iter)->get_type() == IPV4_ADDR_TYPE) ipv4type++;
            else if ((*iter)->get_type() == IPV4_PREFIX_TYPE) ipv4type++;
            else if ((*iter)->get_type() == STRING_TYPE) {
                strtype++;
                continue;
            }
            else {
                PDBG("(%s) is unkown type(%d)!!!!!\n", cmdvec[idx].c_str(), (*iter)->get_type());
                return false;
            }
            if (cmdtype > 1 || inttype > 1 || ipv4type > 1) {   /* FIXME */
                PDBG("!!!!!! (%d, %d, %d)\n", cmdtype, inttype, ipv4type);
                return false;
            }
            cmdptr = (*iter);
        }
        PDBG("+++++++++++++++++++++++++++++++\n");
        if (strtype == num) {   /* FIXME: !!!! */
            PDBG("FIXME: strtype(%d) == partly_match(%d)!!!\n", strtype, num);
            cmdptr = (*cmdlist.begin());
            continue; 
        }
    }

    is_exec = ret_tmp;
    if (is_child) {
        retlist = cmdptr->get_children();
    } else {
        if (cmdptr->get_type() == CMD_TYPE) {
            args.push_back(cmdptr->get_cmd());
        } else 
            args.push_back(cmdvec[idx-1]);
        args.push_back(cmdvec[idx]);
        PDBG("cmdptr[%s], rcmd: %s\n", cmdptr->get_cmd().c_str(), 
                (cmdvec[idx]).c_str());
        cmdptr->find_children(cmdvec[idx], retlist);
    }

    return true;
}


typedef std::vector<cmd_ptr> cmd_vector_t;
typedef boost::shared_ptr<cmd_vector_t> cmd_vector_ptr;
typedef std::vector<cmd_vector_ptr> cmd_hash_t;
typedef cmd_hash_t::iterator    cmd_hash_iterator;

bool cmd_mng::make_cmdtree(cmd_ptr root, const std::string& cmdline, 
        const std::string& hlpline, ExcuteHandle function)
{
    cmd_tree_t cmdtree;
    strvec_ptr  deptvec;
    strvec_ptr  cmdvec(new string_vector());
    strvec_ptr  hlpvec(new string_vector());
    cmd_vector_ptr parents;
    string_vector::iterator  cmditer;
    ExcuteHandle funcptr=NULL;

    cmd_vector_ptr current(new cmd_vector_t());
    cmd_hash_t  cmdhash;

    boost::mutex::scoped_lock lock(_cmd_lock);  /* XXX: */

    kstrtok(hlpline, HELP_DELIM, *hlpvec); 
    kstrtok(cmdline, CMD_DELIM, *cmdvec);

    int cmdnum = 0;
    for (cmditer = cmdvec->begin(); cmditer != cmdvec->end(); cmditer++) {
        deptvec.reset(new string_vector());
        if (is_multi_opt((*cmditer)) == true) {
            if (cmditer == cmdvec->begin()) 
                return false;
            kstrtok((*cmditer), OPT_DELIM, *deptvec);
            cmdtree.push_back(deptvec);
        } else {
            deptvec->push_back(*cmditer);
            cmdtree.push_back(deptvec);
        }
        cmdnum += deptvec->size();
    }
    PDBG("cmdnum: %d, helpnum: %d\n", cmdnum, hlpvec->size());
    if (cmdnum != hlpvec->size()) {
        PDBG("cmdnum(%d) is diff helpnum(%d)!!!", cmdnum, hlpvec->size());
        return false;
    }

    current->push_back(root);
    cmdhash.push_back(current);
    for (int idx=0, hidx=0; idx < cmdtree.size(); idx++) {/* FIXME */
        PDBG("++++++ DEPT(%d) +++++\n", idx);
        current.reset(new cmd_vector_t());
        deptvec = cmdtree[idx];
        parents = cmdhash[idx];
        if (cmdtree.size() == (idx+1)) funcptr = function;
        for (int kdx=0; kdx  < deptvec->size(); kdx++, hidx++) {
            PDBG("============== SUB (%d) hidx(%d) ===============\n", kdx, hidx);
            //cmd_ptr ptr(new command2((*deptvec)[kdx], "TST", funcptr)); 
            cmd_ptr ptr(new command2((*deptvec)[kdx], (*hlpvec)[hidx], funcptr));
            for (int jdx=0; jdx < parents->size(); jdx++) {
                (*parents)[jdx]->add_child(ptr);
            }
            current->push_back(ptr);
        }
        cmdhash.push_back(current);
    }

    return true;
}

